$.fn.reveal2 = function () {

    function getModalManager() {
        var manager = $('body').data('revealManager');
        if (!manager) {

            var $body = $('body');

            var $modalBg = $('.reveal-modal-bg');
            if ($modalBg.length === 0) {
                $modalBg = $('<div />', { 'class': 'reveal-modal-bg' });
                $body.append($modalBg);
                $modalBg.fadeTo('fast', 0.8);
            }

            var animationSpeed = 300;

            var modals = [];

            function onModalsChanged() {
                var toppest = modals.length > 0 ? modals[modals.length - 1] : null;
                if (toppest && toppest.closeOnBackgroundClick) {
                    $modalBg.css({ "cursor": "pointer" });
                } else {
                    $modalBg.css({ "cursor": "auto" });
                }
            }

            $modalBg.bind('click.reveal', function () {
                var toppest = modals.length > 0 ? modals[modals.length - 1] : null;
                if (toppest && toppest.closeOnBackgroundClick) {
                    toppest.close();
                }
            });

            function fadeBackgrondIn() {
                $modalBg.show();
                $modalBg.fadeIn(animationSpeed / 2);
            }

            function fadeBackgrondOut() {
                $modalBg.fadeOut(animationSpeed / 2, function () {
                    $modalBg.hide();
                });
            }

            function isOnTop(modal) {
                if (modals.length == 0) return false;
                return modals[modals.length - 1] === modal;
            }

            function getMaxZIndex() {
                var max = 100;
                for (var i = 0; i < modals.length; i++) {
                    var modal = modals[i];
                    if (modal.zIndex > max) {
                        max = modal.zIndex;
                    }
                }
                return max;
            }
            
            function pushModal(modal) {
                modal.zIndex = getMaxZIndex() + 1;
                modals.push(modal);
                fadeBackgrondIn();
                onModalsChanged();
            }

            function popModal() {
                modals.pop();
                if (modals.length == 0) {
                    fadeBackgrondOut();
                }
                onModalsChanged();
            }

            function getModalsCount() {
                return modals.length;
            }

            manager = {
                isOnTop: isOnTop,
                pushModal: pushModal,
                popModal: popModal,
                getModalsCount: getModalsCount
            };
            $('body').data('revealManager', manager);
        }
        return manager;
    }

    var defaults = {
        animation: 'fadeAndPop', //Possible options: fade, fadeAndPop, none
        animationSpeed: 300,
        closeOnBackgroundClick: true,
        closeOnEscapeKey: true,
        dismissModalClass: 'close-reveal-modal',
        open: function () { },
        opened: $.noop,
        close: $.noop,
        closed: $.noop
    };

    function init($this) {

        var modal = {
            closeOnBackgroundClick: true,
            close: close,
            zIndex: 0
        };

        var dispatcher = $({});

        var opts = $.extend({}, defaults);
        var topMeasure = parseInt($this.css('top'), 10);
        var topOffset = $this.height() + topMeasure;

        var $doc = $(document);

        $('body').bind('keyup.reveal', function (event) {
            if (event.which === 27) {
                if (opts.closeOnEscapeKey) {
                    close();
                }
            }
        });

        $this.find('.' + opts.dismissModalClass).bind('click.reveal', function () {
            close();
        });

        var manager = getModalManager();

        function isOnTop() {
            return manager.isOnTop(modal);
        }

        function open(options) {
            opts = $.extend({}, defaults, options);
            modal.closeOnBackgroundClick = options.closeOnBackgroundClick;

            manager.pushModal(modal);
            $this.css('z-index', modal.zIndex);
            openAnimation();
        }

        function close() {
            if (isOnTop()) {
                closeAnimation();
                //modal.trigger('reveal:close');
            }
        }

        function onOpened() {
            dispatcher.trigger('opened');
            $this.trigger('reveal:opened');
            if (opts.opened) {
                opts.opened();
            }
        }

        function onClosed() {
            $this.css(cssOpts.close);
            manager.popModal();
            dispatcher.trigger('closed');
            $this.trigger('reveal:closed');
        }

        function addOnClosing(handler) {
            dispatcher.bind('closing', handler);
        }

        function addOnClosed(handler) {
            dispatcher.bind('closed', handler);
        }

        var cssOpts = {
            open: {
                'top': 0,  // Set the 'top' property to the document scroll minus the calculated top offset.
                'opacity': 0,
                'visibility': 'visible',
                'display': 'block'
            },
            close: {
                'top': topMeasure,
                'opacity': 1,
                'visibility': 'hidden',
                'display': 'none'
            }
        };

        function openAnimation() {
            $this.addClass("open");

            if (opts.animation === "fadeAndPop") {
                cssOpts.open.top = $doc.scrollTop() - topOffset; // Set the 'top' property to the document scroll minus the calculated top offset.
                cssOpts.open.opacity = 0;
                $this.css(cssOpts.open);

                $this
                    .delay(opts.animationSpeed / 2)
                    .animate({
                        "top": $doc.scrollTop() + topMeasure + 'px', // Set the 'top' property to the document scroll plus the calculated top measure.
                        "opacity": 1
                    },
                    opts.animationSpeed,
                    onOpened);

            }

            if (opts.animation === "fade") {
                cssOpts.open.top = $doc.scrollTop() + topMeasure;
                cssOpts.open.opacity = 0;
                $this.css(cssOpts.open);

                $this
                    .delay(opts.animationSpeed / 2)
                    .animate({
                        "opacity": 1
                    },
                    opts.animationSpeed,
                    onOpened);
            }

            if (opts.animation === "none") {
                cssOpts.open.top = $doc.scrollTop() + topMeasure;
                cssOpts.open.opacity = 1;
                $this.css(cssOpts.open);
                onOpened();
            }
        }

        function closeAnimation() {
            $this.removeClass("open");

            if (opts.animation === "fadeAndPop") {
                $this.animate({
                    "top": $doc.scrollTop() - topOffset + 'px',// Set the top property to the document scrollTop minus calculated topOffset.
                    "opacity": 0

                },
                opts.animationSpeed / 2,
                onClosed);
            }

            if (opts.animation === "fade") {
                $this.animate({ "opacity": 0 },
                  opts.animationSpeed,
                  onClosed);
              }

            if (opts.animation === "none") {
                onClosed();
            }
        }

        return {
            open: open,
            close: close,
            addOnClosing: addOnClosing,
            addOnClosed: addOnClosed
        };
    }

    var data = this.data('reveal');
    if (!data) {
        data = init(this);
        this.data('reveal', data);
    }

    return data;
};
